#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <video_manager.h>
#include <config.h>
#include <linux/videodev2.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <poll.h>

//声明函数
static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf);
static int V4l2PutFrameForStreaming(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf);
static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf);
static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf);
static T_VideoOpr g_tV4l2VideoOpr;

//表示支持的格式，这里并不是表示硬件支持的格式，而是软件支持的格式，我们需要判断硬件是否支持这些格式
static unsigned int g_supportedFormats[] = {V4L2_PIX_FMT_YUYV,V4L2_PIX_FMT_MJPEG,V4L2_PIX_FMT_RGB565,V4L2_PIX_FMT_RGB24,V4L2_PIX_FMT_RGB32};

//查询硬件是否支持上面定义的格式
static int isSupportFormat(unsigned int iPixelFormat,int *Bpp)
{
    switch (iPixelFormat)
    {
        case V4L2_PIX_FMT_YUYV:
        {
            *Bpp = 16;
            return 1;
        }
        case V4L2_PIX_FMT_MJPEG:
        {
            *Bpp = 24;
            return 1;
        }
        case V4L2_PIX_FMT_RGB565:
        {
            *Bpp = 16;
            return 1;
        }
        case V4L2_PIX_FMT_RGB24:
        {
            *Bpp = 24;
            return 1;
        }
        case V4L2_PIX_FMT_RGB32:
        {
            *Bpp = 32;
            return 1;
        }    
        default:
            break;
    }

    return 0;
}

void MyGetDispResolution(int *piWidth,int *piHeight,int *ibpp)
{
    int fd_fb;
    struct fb_var_screeninfo var; 
    
    fd_fb = open("/dev/fb0",O_RDWR);
    if(fd_fb < 0)
    {
        printf("can not open /dev/fb0\n");
        return;
    }

    if(ioctl(fd_fb,FBIOGET_VSCREENINFO,&var) != 0)
    {
        printf("can not get var\n");
        return;
    }

    *piWidth = var.xres;
    *piHeight = var.yres;
    *ibpp = var.bits_per_pixel;
    close(fd_fb);
}


/*下面为V4l2的相关函数*/

static int V4l2InitDevice(char *DeviceName,PT_VideoDevice ptVideoDevice)
{
    struct v4l2_capability cap;  //设备的能力
    struct v4l2_fmtdesc fmtdesc;  //格式描述
    struct v4l2_frmsizeenum fsenum;  //帧大小描述
    struct v4l2_format fmt;   //设置的视频格式
    struct v4l2_requestbuffers ReqBuffer;  //申请buffer
    struct v4l2_buffer buf;   //每一个buffer的描述
    int flag = 0;
    int i;

    int iHeight,iWidth,iBpp;
    
    ptVideoDevice->iFd = open(DeviceName,O_RDWR);
    if(ptVideoDevice->iFd < 0)
    {
        printf("can not open %s\n",DeviceName);
        goto err_exit;
    }
   
    //查询能力
    memset(&cap,0,sizeof(struct v4l2_capability));
    if(ioctl(ptVideoDevice->iFd,VIDIOC_QUERYCAP,&cap) != 0)
    {
        printf("can not get capability.\n");
        goto err_exit;
    }

    //判断是否支持指定的功能
    if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))  //用于确保该设备是个捕获设备
    {
        printf("Video capture not supported\n");
        goto err_exit;
    }
    if(!(cap.capabilities & V4L2_CAP_STREAMING))      //用于确保可以实现mmap内存映射
    {
        printf("Streaming not supported\n");
        flag++;
    }
    if(!(cap.capabilities & V4L2_CAP_READWRITE))      //用于确保可以实现read和write
    {
        printf("Read/Write not supported\n");
        flag++;
    }

    if(flag == 2)
    {
        printf("can not support mmap and read/write\n");
        goto err_exit;
    }
    
    //枚举格式
    memset(&fmtdesc,0,sizeof(struct v4l2_fmtdesc));
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 指定type为"捕获"
    for(fmtdesc.index = 0;;fmtdesc.index++)
    {
        if(ioctl(ptVideoDevice->iFd,VIDIOC_ENUM_FMT,&fmtdesc) != 0)
        {
            printf("can not get format information.\n");
            break;
        }

        if(!isSupportFormat(fmtdesc.pixelformat,&iBpp))
            continue;
        
        
        ptVideoDevice->iPixelFormat = fmtdesc.pixelformat;   //设置当前格式
        memset(&fsenum,0,sizeof(struct v4l2_frmsizeenum));   // 先清空帧大小描述
        fsenum.pixel_format = fmtdesc.pixelformat;
        //一个格式可能支持多种帧大小，所以需要枚举帧大小
        for(fsenum.index = 0;;fsenum.index++)
        {
            if(ioctl(ptVideoDevice->iFd,VIDIOC_ENUM_FRAMESIZES,&fsenum) != 0)  //枚举这种格式所支持的帧大小，帧大小也可以称为分辨率
                break;
            printf("format %s,%d,framesize %d: %d x %d\n",fmtdesc.description,fmtdesc.pixelformat,fsenum.index,fsenum.discrete.width,fsenum.discrete.height);
        }
        break;
    }

    if(ptVideoDevice->iPixelFormat == 0)
    {
        printf("can not support the format\n");
        goto err_exit;
    }

    ptVideoDevice->iBpp = (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_YUYV) ? 16 : \
                            (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_MJPEG) ? 0 :  \
                            (ptVideoDevice->iPixelFormat == V4L2_PIX_FMT_RGB565) ? 16 :  \
                            0;
    //读取LCD的分辨率
    MyGetDispResolution(&iWidth,&iHeight,&iBpp);

    //设置当前格式
    memset(&fmt,0,sizeof(struct v4l2_format));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.pixelformat = ptVideoDevice->iPixelFormat;
    fmt.fmt.pix.width = iWidth;
    fmt.fmt.pix.height = iHeight;
    fmt.fmt.pix.field = V4L2_FIELD_ANY;  //该行代码表示驱动程序可以选择任何字段顺序
    if(ioctl(ptVideoDevice->iFd,VIDIOC_S_FMT,&fmt) != 0)
    {
        printf("can not set format\n");
        goto err_exit;
    }
    else
        printf("real format: %d x %d\n",fmt.fmt.pix.width,fmt.fmt.pix.height);  //打印实际设置的格式，因为驱动程序可能并不支持我们设置的格式，并且会自动调整
    
    //将实际的分辨率保存到结构体中
    ptVideoDevice->iWidth     = fmt.fmt.pix.width;
    ptVideoDevice->iHeight    = fmt.fmt.pix.height;
    ptVideoDevice->iFrameSize = ptVideoDevice->iWidth * ptVideoDevice->iWidth;

    //申请Buffer
    memset(&ReqBuffer,0,sizeof(struct v4l2_requestbuffers));
    ReqBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ReqBuffer.memory = V4L2_MEMORY_MMAP;
    ReqBuffer.count = BUF_SIZE;
    if(ioctl(ptVideoDevice->iFd,VIDIOC_REQBUFS,&ReqBuffer) != 0)
    {
        printf("request buffer failed!\n");
        goto err_exit;
    }
    else
    {
        //申请成功后
        printf("real request buffer size is %d\n",ReqBuffer.count);
    }
    ptVideoDevice->iVideoBufCnt = ReqBuffer.count;

    //将buffer映射到用户空间
    if(cap.capabilities & V4L2_CAP_STREAMING)   //如果支持流式传输，即mmap的话
    {
        g_tV4l2VideoOpr.GetFrame = V4l2GetFrameForStreaming;
        g_tV4l2VideoOpr.PutFrame = V4l2PutFrameForStreaming;
        
        for(i = 0;i < ReqBuffer.count;i++)
        {
            memset(&buf,0,sizeof(struct v4l2_buffer));
            buf.index = i;
            buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            buf.memory = V4L2_MEMORY_MMAP;
            if(ioctl(ptVideoDevice->iFd,VIDIOC_QUERYBUF,&buf) != 0)
            {
                printf("can not query buffer\n");
                goto err_exit;
            }
            
            ptVideoDevice->iVideoBufMaxLen = buf.length;
            ptVideoDevice->iVideoBufPtr[i] = mmap(NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED,ptVideoDevice->iFd,buf.m.offset);
            if(ptVideoDevice->iVideoBufPtr[i] == MAP_FAILED)
            {
                printf("mmap failed!\n");
                goto err_exit;
            }
        }
    }
    else if(cap.capabilities & V4L2_CAP_READWRITE)  //如果支持read和write
    {
        g_tV4l2VideoOpr.GetFrame = V4l2GetFrameForReadWrite;
        g_tV4l2VideoOpr.PutFrame = V4l2PutFrameForReadWrite;
        
        ptVideoDevice->iVideoBufCnt = 1;
        ptVideoDevice->iVideoBufMaxLen = ptVideoDevice->iFrameSize * ptVideoDevice->iBpp / 8;
        ptVideoDevice->iVideoBufPtr[0] = malloc(ptVideoDevice->iVideoBufMaxLen);
        if(ptVideoDevice->iVideoBufPtr[0] == NULL)
        {
            printf("malloc failed!\n");
            goto err_exit;
        }
    }
    
    /*把buffer放入输入队列*/
    for(i = 0;i < ReqBuffer.count;i++)
    {
        memset(&buf,0,sizeof(struct v4l2_buffer));
        buf.index = i;
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if(ioctl(ptVideoDevice->iFd,VIDIOC_QBUF,&buf) != 0)
        {
            printf("failed put buffer to queue\n");
            goto err_exit;
        }
    }

    ptVideoDevice->opr = &g_tV4l2VideoOpr;   //将操作函数指针赋值给结构体
    return 0;

err_exit:
    close(ptVideoDevice->iFd);
    return -1;
}

static int V4l2ExitDevice(PT_VideoDevice ptVideoDevice)
{
    int i;

    if(ptVideoDevice->iVideoBufCnt == 1)
    {
        free(ptVideoDevice->iVideoBufPtr[0]);
        ptVideoDevice->iVideoBufPtr[0] = NULL;
    }
    else
    {
        for(i = 0;i < ptVideoDevice->iVideoBufCnt;i++)
        {
            munmap(ptVideoDevice->iVideoBufPtr[i],ptVideoDevice->iVideoBufMaxLen);
            ptVideoDevice->iVideoBufPtr[i] = NULL;
        }
    }
    
    
    close(ptVideoDevice->iFd);
    return 0;
}

static int V4l2GetFrameForStreaming(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf)
{
    struct pollfd fds;
    struct v4l2_buffer buf;
    int ret;
    /*poll*/
    fds.fd = ptVideoDevice->iFd;
    fds.events = POLLIN;
    fds.revents = 0;

    ret = poll(&fds,1,-1);
    if(ret > 0 && fds.revents & POLLIN)
    {
        memset(&buf,0,sizeof(struct v4l2_buffer));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        if(ioctl(ptVideoDevice->iFd,VIDIOC_DQBUF,&buf) != 0)
        {
            printf("dequeue buf failed!\n");
            return -1;
        }
        ptVideoDevice->iVidoeBufCur = buf.index;

        ptVideoBuf->iPixelFormat   = ptVideoDevice->iPixelFormat;
        ptVideoBuf->tPixelDatas.iWidth         = ptVideoDevice->iWidth;
        ptVideoBuf->tPixelDatas.iHeight        = ptVideoDevice->iHeight;
        ptVideoBuf->tPixelDatas.iBpp           = ptVideoDevice->iBpp;
        ptVideoBuf->tPixelDatas.iLineBytes     = ptVideoDevice->iWidth * ptVideoDevice->iBpp / 8;
        ptVideoBuf->tPixelDatas.iTotalBytes    = buf.bytesused;
        ptVideoBuf->tPixelDatas.aucPixelDatas  = ptVideoDevice->iVideoBufPtr[buf.index];
        return 0;
    }
    else
    {
        printf("poll error!\n");
        return -1;
    }
    
    return 0;
}

static int V4l2PutFrameForStreaming(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf)
{
    struct v4l2_buffer buf;

    memset(&buf,0,sizeof(struct v4l2_buffer));
    buf.index = ptVideoDevice->iVidoeBufCur;
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    
    if(ioctl(ptVideoDevice->iFd,VIDIOC_QBUF,&buf) != 0)
    {
        printf("failed put buffer to queue\n");
        return -1;
    }
    
    return 0;
}

static int V4l2GetFrameForReadWrite(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf)
{
    int ret;

    ret = read(ptVideoDevice->iFd,ptVideoDevice->iVideoBufPtr[0],ptVideoDevice->iVideoBufMaxLen);
    if(ret <= 0)
    {
        printf("read video frame failed!\n");
        return -1;
    }

    ptVideoDevice->iVidoeBufCur = 0;
    ptVideoBuf->iPixelFormat   = ptVideoDevice->iPixelFormat;
    ptVideoBuf->tPixelDatas.iWidth         = ptVideoDevice->iWidth;
    ptVideoBuf->tPixelDatas.iHeight        = ptVideoDevice->iHeight;
    ptVideoBuf->tPixelDatas.iBpp           = ptVideoDevice->iBpp;
    ptVideoBuf->tPixelDatas.iLineBytes     = ptVideoDevice->iWidth * ptVideoDevice->iBpp / 8;
    ptVideoBuf->tPixelDatas.iTotalBytes    = ret;
    ptVideoBuf->tPixelDatas.aucPixelDatas  = ptVideoDevice->iVideoBufPtr[0];
    
    return 0;
}

static int V4l2PutFrameForReadWrite(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf)
{
    return 0;
}

/*
static int V4l2GetFrame(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf)
{
    printf("GetFrame\n");
    return 0;
}


static int V4l2PutFrame(PT_VideoDevice ptVideoDevice,PT_VideoBuf ptVideoBuf)
{
    printf("PutFrame\n");
    return 0;
}
*/

static int V4l2StartDevice(PT_VideoDevice ptVideoDevice)
{
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    
    if(ioctl(ptVideoDevice->iFd,VIDIOC_STREAMON,&type) != 0)  //VIDIOC_STREAM(ON)，最后的on表示打开
    {
        printf("can't open capture!\n");
        return -1;
    }
    return 0;
}

static int V4l2StopDevice(PT_VideoDevice ptVideoDevice)
{
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    
    if(ioctl(ptVideoDevice->iFd,VIDIOC_STREAMOFF,&type) != 0) 
    {
        printf("can't stop capture!\n");
        return -1;
    }
    return 0;
}

/*构造一个VideoOpr结构体*/
static T_VideoOpr g_tV4l2VideoOpr = {
    .name = "v4l2",
    .InitDevice  = V4l2InitDevice,
    .ExitDevice  = V4l2ExitDevice,
    //.GetFrame    = V4l2GetFrame,
    //.PutFrame    = V4l2PutFrame,
    .StartDevice = V4l2StartDevice,
    .StopDevice  = V4l2StopDevice,
};


/*注册这个结构体*/
int V4l2Init()
{
    return RegisterVideoOpr(&g_tV4l2VideoOpr);
}